<!-- eslint-disable prefer-destructuring -->
<!-- eslint-disable no-unused-vars -->
<!-- eslint-disable no-unused-vars -->
<template>
  <!-- 软件仓库升级包上传 -->
  <!-- 特殊处：在表格中，数据倒序插入，影响展示顺序 -->
  <div style="position: relative">
    <template v-if="showDom && !recordData.id && recordData.editable">
      <a @click="upload">{{ $t('upload') }}</a>
    </template>
    <template v-if="recordData[fileStoreField] && recordData[fileStoreField].uploadLoadding">
      <a-progress
        v-if="recordData[fileStoreField].percent"
        class="progressCss"
        :percent="recordData[fileStoreField].percent"
      />
      <span class="prepareStyle">
        <i class="iconfont icon-a-zhengzaijiazaiload loading"></i>
        <template v-if="recordData[fileStoreField].percent">{{ $t('uploaddingtip') }}</template>
        <template v-else>{{ $t('preUpload') }}</template>
      </span>
    </template>

    <input v-show="false" ref="upload" type="file" :accept="accept ? accept : '*'" @change="fileChange" />
    <div v-if="showDom">
      <table-ellipsis
        :tableEllipsisIsTurnLine="false"
        v-if="!recordData.id && recordData[fileStoreField].fileName"
        :text="recordData[fileStoreField].fileName"
      ></table-ellipsis>
    </div>
  </div>
</template>

<script>
import TableEllipsis from '@/components/Ellipsis/TableEllipsis'
import SparkMD5 from 'spark-md5'
import CryptoJS from 'crypto-js'
import { mapState } from 'vuex'
// 上传文件组件
export default {
  name: 'ChunkUpload',
  components: { TableEllipsis },
  props: {
    // 软件管控新增属性，控制用新增的接口
    userPartUrl: {
      type: Boolean,
      default: false
    },
    // 是否dom样式改变
    showDom: {
      type: Boolean,
      default: true
    },
    // 该文件上传成功后存储信息的字段
    fileStoreField: {
      type: String,
      required: true
    },
    // 父组件行数据
    recordData: {
      type: Object,
      default: () => {
        return {}
      }
    },
    // 判断上传保存地址1本地 2FTP 6腾讯云 7阿里云 8华为云
    uploadType: {
      type: String,
      default: '1'
    },
    // 上传类型限制
    accept: {
      type: Array,
      default: () => {
        return []
      }
    },
    // 限制大小
    size: {
      type: Number,
      default: 10000 * 1024 * 1024
    },
    // 上传及合并完成是否响应时间
    finishTag: {
      type: Boolean,
      default: false
    },
    // 手动填写摘要算法
    hashAlgorithm: {
      type: Number,
      default: 1
    },
    // 限制名称
    limitFileName: {
      type: String,
      default: ''
    },
  },
  data () {
    return {
      file: {}, // 选取的文件
      chunkSize: 30 * 1024 * 1024, // 分块大小
      chunks: 0, // 块数
      currentChunk: 0, // 当前属于第几块
      fileReader: new FileReader(), // 读取文件内容
      chunkMD5s: [], // 每一块MD5集合
      actualChunkSize: 0, // 当软件管控的文件小于3*chunkSize的大小时，需要重新计算分片大小进行分片
      isStopUpload: false // 判断是否终止上传
    }
  },
  computed: {
    acceptStr () {
      return this.accept.toString()
    },
    ...mapState({
      // 摘要算法
      digestAlgorithm: (state) => state.dict.digestAlgorithm
    })
  },
  methods: {
    fileChange (event) {
      const vm = this
      vm.initialData()
      // eslint-disable-next-line prefer-destructuring
      vm.file = event.target.files[0]
      if(vm.limitFileName && vm.file.name!==vm.limitFileName){
        vm.$message.warning(`文件名只能为${vm.limitFileName}`)
        return
      }
      // 验证文件类型
      const length1 = vm.file.name.lastIndexOf('.') // 取到文件名开始到最后一个点的长度
      const { length } = vm.file.name // 文件名总长度
      const fileFormat = vm.file.name.substring(length1, length) // 截取文件格式
      if (vm.accept.length > 0) {
        // eslint-disable-next-line no-unused-vars
        const tag = vm.accept.findIndex((value, index, arr) => {
          return value === fileFormat
        })
        if (tag === -1) {
          this.$message.warning(`${vm.$t('pleaseUpload')} ${vm.acceptStr} ${vm.$t('typesOf')}`)
          event.target.value = null;
          vm.initialData()
          return
        }
      }
      //   限制大小
      if (vm.size && vm.size < vm.file.size) {
        this.$message.warning(`${vm.$t('fileSizeLimit')}${vm.size / 1024 / 1024}M`)
        vm.initialData()
        event.target.value = null;
        return
      }
      // 解析apk信息
      if (fileFormat === '.apk') {
        vm.parsingApk()
      }
      // 软件管控安全要求，小文件也要分至少三块（size按byte字节计算，所以文件大小不能小于3b
      if (vm.userPartUrl) {
        if (vm.file.size < 3) {
          vm.$message.warning(`${vm.$t('uploadFileTip')}`)
        } else {
          if (Math.ceil(vm.file.size / vm.chunkSize) < 3) {
            vm.chunks = 3
            vm.actualChunkSize = Math.floor(vm.file.size / 3)
          } else {
            vm.chunks = Math.ceil(vm.file.size / vm.chunkSize)
          }
          vm.uploadProgress()
        }
      } else {
        vm.chunks = Math.ceil(vm.file.size / vm.chunkSize)
        if (vm.chunks > 0) {
          // 记录文件信息 开始分块
          vm.uploadProgress()
        } else {
          this.$message.warning(`${vm.$t('uploadFileTip')}`)
          vm.initialData()
          event.target.value = null;
        }
      }
    },
    uploadProgress () {
      const vm = this
      vm.resetUpload() // 避免相同文件无法再次上次
      vm.$set(vm.recordData[vm.fileStoreField], 'fileName', vm.file.name)
      vm.$set(vm.recordData[vm.fileStoreField], 'size', vm.file.size)
      vm.$set(vm.recordData[vm.fileStoreField], 'percent', 0) // 预估开始分块准备工作
      vm.$set(vm.recordData[vm.fileStoreField], 'contentType', vm.file.type)
      vm.$set(vm.recordData[vm.fileStoreField], 'chunkNum', 0) // 记录上传成功数量，用于进度展示
      const spark = new SparkMD5.ArrayBuffer()
      const sha256 = CryptoJS.algo.SHA256.create()
      vm.$set(vm.recordData[vm.fileStoreField], 'uploadLoadding', true)
      vm.$emit('uploadStart')
      vm.loadNext()
      vm.fileReader.onload = function (e) {
        spark.append(e.target.result) // Append array buffer
        vm.chunkMD5s.push(SparkMD5.ArrayBuffer.hash(e.target.result)) // 当前块的md5
        sha256.update(vm.arrayBufferToWordArray(e.target.result))
        vm.currentChunk = vm.currentChunk + 1
        if (vm.currentChunk < vm.chunks) {
          // 把文件进行分块
          vm.loadNext()
        } else {
          // 文件分块完成 开始文件初始化 计算md5和sha256
          vm.$set(vm.recordData[vm.fileStoreField], vm.digestAlgorithm[1], spark.end())
          const sha256res = sha256.finalize()
          const sha256Str = sha256res.toString(CryptoJS.enc.Hex).toLowerCase()
          vm.$set(vm.recordData[vm.fileStoreField], vm.digestAlgorithm[2], sha256Str)
          if (vm.isStopUpload) return
          vm.chunkInit().then((res) => {
            // 进行上传
            vm.runTasks(res, 3)
          })
        }
      }
      vm.fileReader.onerror = function () {
        vm.$set(vm.recordData[vm.fileStoreField], 'uploadLoadding', false)
      }
    },
    // 对文件进行分块读取
    loadNext () {
      //   对文件进行分块的函数
      const vm = this
      if (!vm.file) {
        return
      }
      const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      if (vm.actualChunkSize) {
        const start = vm.currentChunk * vm.actualChunkSize
        const end = (vm.currentChunk === 2) ? vm.file.size : (start + vm.actualChunkSize)
        vm.fileReader.readAsArrayBuffer(blobSlice.call(vm.file, start, end)) // 读取文件会触发fileReader.onload
      } else {
        const start = vm.currentChunk * vm.chunkSize
        const end = start + vm.chunkSize >= vm.file.size ? vm.file.size : start + vm.chunkSize
        vm.fileReader.readAsArrayBuffer(blobSlice.call(vm.file, start, end)) // 读取文件会触发fileReader.onload
      }
    },
    // 分块上传之前对文件初始化
    chunkInit () {
      const vm = this
      const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      const chunkUploadArray = []
      return new Promise((resolve) => {
        vm.$api
          .chunkInit({
            chunkSize: vm.actualChunkSize ? vm.actualChunkSize : vm.chunkSize, // 分块大小
            fileName: vm.recordData[vm.fileStoreField].fileName,
            contentType: vm.recordData[vm.fileStoreField].contentType,
            size: vm.recordData[vm.fileStoreField].size,
            hashAlgorithm: vm.hashAlgorithm,
            checksum: vm.recordData[vm.fileStoreField][vm.digestAlgorithm[vm.hashAlgorithm]]
          })
          .then((res) => {
            vm.$set(vm.recordData[vm.fileStoreField], 'identifier', res.data)
            for (let i = 0; i < vm.chunks; i = i + 1) {
              if (!vm.file) {
                break
              }
              const start = i * (vm.actualChunkSize ? vm.actualChunkSize : vm.chunkSize)
              let end = 0
              if (vm.actualChunkSize) {
                if (i === 2) {
                  end = vm.file.size
                } else {
                  end = start + vm.actualChunkSize
                }
              } else {
                end = Math.min(vm.file.size, start + vm.chunkSize)
              }
              // 构建表单
              const form = new FormData()
              form.append('file', blobSlice.call(vm.file, start, end))
              form.append('identifier', res.data)
              form.append('chunkNumber', i + 1) // 当前是第一块
              form.append('checksum', vm.chunkMD5s[i]) // 当前块md5
              form.append('hashAlgorithm', '1') // 分块固定使用md5
              // 加入到 Promise 数组中
              chunkUploadArray.push({ form })
            }
            resolve(chunkUploadArray)
          })
          .catch(() => {
            vm.initialData()
            vm.resetUpload() // 重置后，避免影响想同文件上传失败后再次上次
          })
      })
    },
    async runTasks (tasks, steps) {
      const _this = this
      for (let i = 0; i < tasks.length; i = i + steps) {
        await _this.stepsRunTask(tasks, i, steps)
      }
    },
    // 几块同步请求
    stepsRunTask (tasks, start, steps) {
      return new Promise((resolve, reject) => {
        const length = start + steps
        const vm = this
        const multipleSuccess = []
        for (let i = start; i < length && i < tasks.length; i = i + 1) {
          multipleSuccess.push(vm.fileuploadWithRetryingResume(tasks[i].form, 0))
        }
        Promise.all(multipleSuccess)
          .then(() => {
            resolve()
          })
          .catch((err) => {
            reject(err)
          })
      })
    },
    // 分块上传（带重试）
    fileuploadWithRetryingResume (form = {}, errNum = 0) {
      const vm = this
      return new Promise((resolve, reject) => {
        // 软件管控分片上传
        if (vm.userPartUrl) {
          vm.$api
            .chunkPartUpload(form)
            .then((res) => {
            //  记录上传成功的块数，并计算上传真实进度
              vm.$set(vm.recordData[vm.fileStoreField], 'chunkNum', vm.recordData[vm.fileStoreField].chunkNum + 1)
              let percetNum = 0
              if (vm.recordData[vm.fileStoreField].chunkNum === vm.chunks) {
                percetNum = vm.recordData[vm.fileStoreField].size
              } else {
                percetNum = vm.recordData[vm.fileStoreField].chunkNum * vm.chunkSize
              }
              vm.next((percetNum / vm.recordData[vm.fileStoreField].size) * 100)
              resolve(res)
            })
            .catch((err) => {
            // 续传重试5次(5s后开始重新上传)
              if (errNum <= 5) {
                vm.$message.info(vm.$t('retrying_resume'))
                setTimeout(() => {
                  vm.fileuploadWithRetryingResume(form, errNum + 1).then(() => {
                    resolve()
                  })
                }, 5000)
              } else {
                vm.$message.info(vm.$t('upload_failed'))
                vm.initialData()
                vm.resetUpload() // 重置后，避免影响想同文件上传失败后再次上次
                reject(err)
              }
            })
        } else {
          // OTA上传
          vm.$api
            .chunkUpload(form)
            .then((res) => {
            //  记录上传成功的块数，并计算上传真实进度
              vm.$set(vm.recordData[vm.fileStoreField], 'chunkNum', vm.recordData[vm.fileStoreField].chunkNum + 1)
              let percetNum = 0
              if (vm.recordData[vm.fileStoreField].chunkNum === vm.chunks) {
                percetNum = vm.recordData[vm.fileStoreField].size
              } else {
                percetNum = vm.recordData[vm.fileStoreField].chunkNum * vm.chunkSize
              }
              vm.next((percetNum / vm.recordData[vm.fileStoreField].size) * 100)
              resolve(res)
            })
            .catch((err) => {
            // 续传重试5次(5s后开始重新上传)
              if (errNum <= 5) {
                vm.$message.info(vm.$t('retrying_resume'))
                setTimeout(() => {
                  vm.fileuploadWithRetryingResume(form, errNum + 1).then(() => {
                    resolve()
                  })
                }, 5000)
              } else {
                vm.$message.info(vm.$t('upload_failed'))
                vm.initialData()
                vm.resetUpload() // 重置后，避免影响想同文件上传失败后再次上次
                reject(err)
              }
            })
        }
      })
    },
    // 进度条
    next (res) {
      const vm = this
      vm.$set(vm.recordData[vm.fileStoreField], 'percent', parseInt(res, 10))
      if (parseInt(res, 10) === 100) {
        vm.mergeChunks()
      }
    },
    // 合并
    mergeChunks () {
      const vm = this
      // 合并chunks
      const data = {
        identifier: vm.recordData[vm.fileStoreField].identifier,
        uploadType: vm.uploadType
      }
      // 软件管控分片合并
      if (vm.userPartUrl) {
        vm.$api
          .filePartMerge(data)
          .then((res) => {
            vm.$set(vm.recordData[vm.fileStoreField], 'uploadLoadding', false)
            vm.$set(vm.recordData[vm.fileStoreField], 'fileId', res.data)
            vm.$emit('uploadDone')
          })
          .catch(() => {
          // 合并失败后要初始化
            vm.initialData()
            vm.resetUpload() // 重置后，避免影响想同文件上传失败后再次上次
          })
          .finally(() => {
            if (vm.finishTag) {
              vm.$emit('closeModel')
            }
          })
      } else {
        // OTA分片合并
        vm.$api
          .fileMerge(data)
          .then((res) => {
            vm.$set(vm.recordData[vm.fileStoreField], 'uploadLoadding', false)
            vm.$set(vm.recordData[vm.fileStoreField], 'fileId', res.data)
          })
          .catch(() => {
          // 合并失败后要初始化
            vm.initialData()
            vm.resetUpload() // 重置后，避免影响想同文件上传失败后再次上次
          })
          .finally(() => {
            if (vm.finishTag) {
              vm.$emit('closeModel')
            }
          })
      }
    },
    upload () {
      this.$refs.upload.click()
    },
    // 初始化数据
    initialData () {
      const vm = this
      vm.$set(vm.recordData, vm.fileStoreField, {})
      vm.$set(vm.recordData[vm.fileStoreField], 'uploadLoadding', false)
      vm.$set(vm.recordData[vm.fileStoreField], 'percent', 0)
      vm.$set(vm.recordData[vm.fileStoreField], 'mergeFlag', true)
      vm.file = null
      vm.chunkSize = 30 * 1024 * 1024
      vm.chunks = 0
      vm.actualChunkSize = 0
      vm.currentChunk = 0
      vm.fileReader = new FileReader()
      vm.chunkMD5s = []
    },
    // 解析apk文件
    parsingApk () {
      const vm = this
      const AppInfoParser = require('app-info-parser')
      const parser = new AppInfoParser(vm.file) // or xxx.ipa
      parser.parse().then((result) => {
        vm.$set(vm.recordData[vm.fileStoreField], 'packageName', result.package) // 包名称
        vm.$set(vm.recordData[vm.fileStoreField], 'versionCode', result.versionCode) // 版本编号
        vm.$set(vm.recordData[vm.fileStoreField], 'versionName', result.versionName) // 版本号
        vm.$set(vm.recordData[vm.fileStoreField], 'minSdkVersion', result.usesSdk.minSdkVersion) // 系统要求
        vm.$set(vm.recordData[vm.fileStoreField], 'appName', result.application.label[0]) // 应用名称
      })
    },
    arrayBufferToWordArray (value) {
      const i8a = new Uint8Array(value)
      const a = []
      for (let i = 0; i < i8a.length; i += 4) {
        a.push((i8a[i] << 24) | (i8a[i + 1] << 16) | (i8a[i + 2] << 8) | (i8a[i + 3]))
      }
      return CryptoJS.lib.WordArray.create(a, i8a.length)
    },
    // 置空input
    resetUpload () {
      this.$refs.upload.value = ''
    }
  },
  beforeDestroy () {
    this.isStopUpload = true
    this.file = null
  }
}
</script>

<style lang='less' scoped>
.progressCss {
  position: absolute;
  top: -6px;
  left: 0;
  width: 100%;
}
.prepareStyle {
  font-size: 0.8rem;
}
.loading {
  animation: spin  2s infinite;
  -webkit-animation: spin 2s  infinite;
  display: inline-block;
}
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
@-webkit-keyframes spin {
  from {
    -webkit-transform: rotate(0deg);
  }
  to {
    -webkit-transform: rotate(360deg);
  }
}
</style>
